#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/wait.h>
#include <linux/cdev.h>
#include <linux/poll.h>
#include <linux/mutex.h>
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/io.h>
#include <linux/mm.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/slab.h>
#include <linux/platform_device.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#include "leddriver.h"

int  major = 11, minor = 0;
int  leds_driver_num = 1;

struct leds_driver_dev {
	struct cdev mydev;
	struct mutex mtx;
	unsigned led[4];
};

struct class *cls;
struct device *pdev;
struct leds_driver_dev *pgmydev;

int leds_driver_open(struct inode *pnode, struct file *pfile)
{
	pfile->private_data = (void*)container_of(pnode->i_cdev, struct leds_driver_dev, mydev);
	printk("leds_driver_open is called\n");
	return 0;
}

int leds_driver_close(struct inode *pnode, struct file *pfile)
{
	printk("leds_driver_close is called\n");
	return 0;
}

long leds_driver_ioctl(struct file *pfile, unsigned int cmd, unsigned long arg)
{
	int lednum = (int)arg - 2;
	// printk("led_driver_ioctl is called\n");
	// printk("lednum = %d\n", lednum);
	struct leds_driver_dev *pmydev = (struct leds_driver_dev*)pfile->private_data;
	if(lednum<0 || lednum>=4)
	{
		printk("Led num is unkown\n");
		return -1;
	}
	mutex_lock(&pmydev->mtx);
	switch (cmd)
	{
	case LED_IOCTL_ON:
		gpio_set_value(pmydev->led[lednum], 1);
		// gpio_direction_output(pmydev->led[lednum], 1);
		break;
	case LED_IOCTL_OFF:
		gpio_set_value(pmydev->led[lednum], 0);
		// gpio_direction_output(pmydev->led[lednum], 0);
		break;
	case LED_IOCTL_TOGGLE:
		gpio_set_value(pmydev->led[lednum], !gpio_get_value(pmydev->led[lednum]));
		// gpio_direction_output(pmydev->led[lednum], !gpio_get_value(pmydev->led[lednum]));
		break;
	default:
		printk("The cmd is unkown\n");
		break;
	}
	mutex_unlock(&pmydev->mtx);
	return 0;
}

struct file_operations myops = {
	.owner = THIS_MODULE,
	.open = leds_driver_open,
	.release = leds_driver_close,
	.unlocked_ioctl = leds_driver_ioctl
};

void led_init(struct leds_driver_dev *pmydev, struct device_node *pnode)
{
	int lednum = 0;
	
	pmydev->led[0] = of_get_named_gpio(pnode, "led2-gpio", 0);
	gpio_request(pmydev->led[0], "led2");
	pmydev->led[1] = of_get_named_gpio(pnode, "led3-gpio", 0);
	gpio_request(pmydev->led[1], "led3");
	pmydev->led[2] = of_get_named_gpio(pnode, "led4-gpio", 0);
	gpio_request(pmydev->led[2], "led4");
	pmydev->led[3] = of_get_named_gpio(pnode, "led5-gpio", 0);
	gpio_request(pmydev->led[3], "led5");

	for(lednum = 0; lednum < 4; lednum++)
	{
		gpio_direction_output(pmydev->led[lednum], 0);
	}

	printk("led_init finished\n");
}

void led_free(struct leds_driver_dev *pmydev)
{
	int lednum = 0;
	for(lednum = 0; lednum < 4; lednum++)
	{
		gpio_free(pmydev->led[lednum]);
	}
}

int led_driver_probe(struct platform_device *p_pltdev)
{
	int ret = 0;
	int i = 0;
	struct device_node *pnode = p_pltdev->dev.of_node;
	dev_t devno = MKDEV(major, minor);
	ret = register_chrdev_region(devno, leds_driver_num, "leddriver");
	if(ret)
	{
		ret = alloc_chrdev_region(&devno, minor, leds_driver_num, "leddriver");
		if(ret)
		{
			printk("get devno failed!\n");
			return -1;
		}
		major = MAJOR(devno);
		minor = MINOR(devno);
	}
	printk("register succeed!(%d, %d)\n", major, minor);

	pgmydev = kmalloc(sizeof(struct leds_driver_dev), GFP_KERNEL);

	for(i=0; i<leds_driver_num; i++)
	{
		devno = MKDEV(major, minor+i);
		cdev_init(&pgmydev->mydev, &myops);
		pgmydev->mydev.owner = THIS_MODULE;
		cdev_add(&pgmydev->mydev, devno, 1);
	}

	cls = class_create(THIS_MODULE, "fsled");
	if(cls == NULL)
	{
		printk("class_create failed\n");
		cdev_del(&pgmydev->mydev);
		unregister_chrdev_region(devno, leds_driver_num);
	}

	pdev = device_create(cls, NULL, devno, NULL, "fsled%d", 0);
	if(pdev == NULL)
	{
		printk("device_create failed\n");
		class_destroy(cls);
		cdev_del(&pgmydev->mydev);
		unregister_chrdev_region(devno, leds_driver_num);
	}

	mutex_init(&pgmydev->mtx);
	led_init(pgmydev, pnode);

	return 0;
}

int led_driver_remove(struct platform_device *p_pltdev)
{
	dev_t devno = MKDEV(major, minor);
	led_free(pgmydev);
	device_destroy(cls, devno);
	class_destroy(cls);
	cdev_del(&pgmydev->mydev);
	unregister_chrdev_region(devno, leds_driver_num);
	kfree(pgmydev);
	pgmydev = NULL;
	printk("led_driver_remove is called\n");
	return 0;
}

struct of_device_id led_dts[] = {
	{.compatible = "fs4412,led2-5"},
	{}
};

struct platform_device_id led_ids[] = {
	[0] = {"led2-5", 0},
	[1] = {}
};

struct platform_driver led_driver = {
	.driver = {
		.name = "leddriver",
		.owner = THIS_MODULE,
		.of_match_table = led_dts
	},
	.probe = led_driver_probe,
	.remove = led_driver_remove,

	.id_table = led_ids
};

module_platform_driver(led_driver);
MODULE_LICENSE("GPL");

